Coding Guidelines - SQL

Rules and recommendations for developing using the SQL language.

Definitions

SQL

Structured Query Language

MDF

Main Database File. File contains all actual data. For example: DatabaseName.mdf

LDF

Log Data File. File contains all actions which has been done with data and still in progress (If FULL of BULK logging mode is enabled) (For example: DatabaseName.LDF). If you have not truncated your log file from the beginning of database creation then you can recreate *.MDF file at any point in the time.

Page

A unit of storage. Specifically speaking with regard to to SQL Server, a page is 8K in size and is the smallest unit of I/O in terms of data. Server moves data in chunks called 'Pages' because of caching.

When a table or log is stored on disk, it is stored in 8K chunks (and most of the time SQL Server allocates 8 – 8K chunks to objects). A 64KB block of the database is called an extent. SQL Server allocates extents once an object reaches a minimum size (which is also 64KB) to try to keep an object more contiguous.

Dirty Page

Data or log page modified after entered into the buffer cache, but the modifications have not yet been written to disk.

CHECKPOINT

Command which forces all dirty pages for the current database to be written to disk.

SQL Server 2000 always generates automatic checkpoints. The interval between automatic checkpoints is based on the number of records in the log, not time. The time interval between automatic checkpoints can be highly variable. Checkpoints truncate the unused/inactive portion of the transaction log if the database is using the SIMPLE recovery model. The log is not truncated by checkpoints if the database is using the FULL or BULK recovery models.

Heap

Table without a clustered index.

Redundant Index

Clustered index on columns that is already "covered".

DML

Data Manipulation Language. Commands used to update, insert, and delete records.

The most important DML statements in SQL are:

SELECT

Extracts data from a database table.

UPDATE

Updates data in a database table.

DELETE

Deletes data from a database table.

INSERT INTO

Inserts new data into a database table.

DDL

Data Definition Language. Commands used to create, alter or delete database tables.

You can also define indexes (keys), specify links between tables, and impose constraints between database tables. The most important DDL statements in SQL are:

CREATE TABLE

Creates a new database table.

ALTER TABLE

Alters (changes) a database table.

DROP TABLE

Drops (deletes) a database table.

CREATE INDEX

Creates an index (search key).

DROP INDEX

Drops (deletes) an index.

TRANSACTION

One action or set of actions on SQL Database.

Actions can be done with commands of DML or DDL Programming Languages.

Listed DML and DDL statements above, if single and NOT enclosed in BEGIN TRAN…COMMIT TRAN block are treated by SQL server as an AUTO COMMIT transactions. You can wrap commands into one set:

-- Set lock type for current session which will be used inside transaction (described below).
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
GO
BEGIN TRANSACTION
    SELECT * FROM table1
    SELECT * FROM table2
    ...
COMMIT TRANSACTION
-- Set lock type for current session to default value (described below).
SET TRANSACTION ISOLATION LEVEL READ COMMITTED

Phantom data

New data inserted inside transaction.

Dirty data

Data updated inside transaction.

Dirty read

Action then transaction tries to read dirty data created by other transaction.

Isolation

Restricting access to database items by locking them (tables, rows...).

When SQL Server executes TRANSACTION it isolates parts of data inside database so for example two users can't UPDATE one data row at the same time. Isolation is done by locking database items. These are isolation types:

READ COMMITTED

Default lock type. Specifies that shared locks are held while the data is being read to avoid dirty reads, but the data can be changed before the end of the transaction, resulting in non repeatable reads or phantom data. This option is the SQL Server default.

READ UNCOMMITTED

Implements dirty read, or isolation level 0 locking, which means that no shared locks are issued and no exclusive locks are honoured. When this option is set, it is possible to read uncommitted or dirty data; values in the data can be changed and rows can appear or disappear in the data set before the end of the transaction. This option has the same effect as setting NOLOCK on all tables in all SELECT statements in a transaction. This is the least restrictive of the four isolation levels.

REPEATABLE READ

Locks are placed on all data that is used in a query, preventing other users from updating the data, but new phantom rows can be inserted into the data set by another user and are included in later reads in the current transaction. Because concurrency is lower than the default isolation level, use this option only when necessary.

SERIALIZABLE

Places a range lock on the data set, preventing other users from updating or inserting rows into the data set until the transaction is complete. This is the most restrictive of the four isolation levels. Because concurrency is lower, use this option only when necessary. This option has the same effect as setting HOLDLOCK on all tables in all SELECT statements in a transaction.

Exclusive lock

This lock mode is very straightforward, a group of records are taken, (row, page, extent, table or database), and are held exclusively by one transaction. By default when an INSERT, UPDATE, or DELETE statement runs, an Exclusive Lock will be issued. No other operation of any kind, (read or DML), can use the records held by an this lock.

Shared lock

Applied on records read by a SELECT statement. It is designed to allow concurrent read access from other transactions, but none can modify the held records. This lock enables reads to comply with ACID by disallowing records to be changed at the same instant a read occurs. Only committed data can be read. Shared lock manipulation is one of the areas we will gain performance by controlling.

Update Lock

An Update Lock stops this type of deadlock from occurring. When a transaction signals intent to convert, an Update lock is requested. Only one transaction can obtain an Update lock on the selected resource at a time. When the records are actually modified, the Update lock will be converted to an Exclusive lock. By only allowing one transaction at a time to obtain an Update lock, the deadlocking on conversion requests is eliminated. This type of lock can be understood by looking at an update statement with a WHERE clause. Before the update can complete, the records meeting the WEHRE clause must be selected. Rather than use a shared lock for this initial select, an Update lock will be requested.

Deadlock

Condition in which one resource is waiting on the action of a second, while that second action is waiting on the first.

A deadlock on the other hand, means there is no way to finish. Your transaction is stuck in a loop with some other transaction. At this point, the database system will usually pick one transaction to be killed so the other can complete.

Rules and Naming

Naming Rules

Data Type

Enum Tables

C# enumerations can be stored on Database under [Types] schema by using this pattern:

public enum [TableName]
{
	///<summary>[Summary]</summary>
	[Description("[Description]")]
	[Value] = [Id],
}
CREATE TABLE [Types].[TableName] (
	[Id]          INT           NOT NULL,
	[Value]       VARCHAR (20)  NOT NULL,
	[Description] VARCHAR (50)  NOT NULL,
	[Summary]     VARCHAR (100) NOT NULL,
	CONSTRAINT [PK_Types.TableName] PRIMARY KEY CLUSTERED ([Id] ASC)
);

Common Column Names

Name Definition
Id [uniqueidentifier|bigint|int] NOT NULL DEFAULT (newid()) Record primary key
OtherTableId [uniqueidentifier|bigint|int] NOT NULL Foreign key - primary key in [OtherTable]
Created [datetime] NOT NULL DEFAULT (getdate()) Record create time
Updated [datetime] NOT NULL DEFAULT (getdate()) Record update time
IsEnabled [bit] NOT NULL DEFAULT ((1)) Record is enabled
Checksum [timestamp] NOT NULL Checksum (or time stamp) to check if row changed before new update

Examples

Table.Column[Customer].[Id]
C# DataContext propertypublic virtual DataSet<Customer> Customer { get; set; }
C# Code var customer    = db.Customer.First();
var customers   = db.Customer.All();
var customerId  = customer.Id;
var customerIds = customers.Select(x => x.Id).ToArray();

DateTime Tips

  1. SQL 2000 Server must generate dates with GETUTCDATE() instead of GETDATE() – in this way DB server will be completely independent from Time Zone settings of PC itself. SQL 2005 have TimeZone support already.
  2. Date times must be passed between servers with time zone offsets. In this case both sides can use this extra information for proper Date Time conversions (it means that date can be submitted by any server from any Time Zone and it will be converted correctly on other side always.
  3. If date times are submitted as strings (only thru GET and POST requests!) then date times must be send in proper format as described by ISO 8601 International Standard: yyyy-MM-ddThh:mm:ss.fff ±hh:ss
  4. On ASP JavaScript DateTime values from different time zones can be compared as integers:
    // These dates are same: 2000-01-01 00:00:00-0800 = 2000-01-01 08:00:00Z  = 949392000000
    var IsSameDateTime = (date1. getTime() = = date2 .getTime() );
  5. To format date as yyyy-MM-dd you can use:
    REPLACE ( STR ( DATEPART ( yyyy , @datetime ) , 4 ) + '-' + STR ( DATEPART ( mm , @datetime ) , 2 ) + '-' + STR ( DATEPART ( dd , @datetime ) , 2 ) , ' ' , '0' )

By following these advices code can be made time zone independent and there will be no need to play with time zones.

General Tips

Performance Tips

SQL Managed Access

If you want proper managed access to SQL servers from .NET code then use these libraries with Microsoft Visual Studio:

Microsoft SQL Server Native Client
http://msdn.microsoft.com/data/ref/sqlnative/default.aspx

Microsoft SQL Server Native Client (SQL Native Client) is a single dynamic-link library (DLL) containing both the SQL OLE DB provider and SQL ODBC driver. It contains run-time support for applications using native-code APIs (ODBC, OLE DB and ADO) to connect to Microsoft SQL Server 7.0, 2000 or 2005. SQL Native Client should be used to create new applications or enhance existing applications that need to take advantage of new SQL Server 2005 features. This redistributable installer for SQL Native Client installs the client components needed during run time to take advantage of new SQL Server 2005 features, and optionally installs the header files needed to develop an application that uses the SQL Native Client API.

Microsoft SQL Server 2005 Management Objects Collection
http://go.microsoft.com/fwlink/?linkid=54583#snac

The Management Objects Collection package includes several key elements of the SQL Server 2005 management API, including Analysis Management Objects (AMO), Replication Management Objects (RMO), and SQL Server Management Objects (SMO). Developers and DBAs can use these components to programmatically manage SQL Server 2005. Microsoft SQL Server 2005 Management Objects Collection requires Microsoft Core XML Services (MSXML) 6.0 and Microsoft SQL Server Native Client.

 Code example on how to get text of stored procedure:

string connectionString = "Data Source=localhost;Integrated Security=True";
System.Data.SqlClient.SqlConnectionStringBuilder stringBuilder;
stringBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder(connectionString);
Microsoft.SqlServer.Management.Smo.Server server;
server = new Microsoft.SqlServer.Management.Smo.Server(stringBuilder.DataSource);
Microsoft.SqlServer.Management.Smo.StoredProcedure procedure;
procedure = server.Databases["DataseName"].StoredProcedures["ProcedureName"];
// Get all text of of procedure.
string text = procedure.Parameters["Text"].ToString();

Not all propeerties are available for SQL 2005 Server. You can check server version with:

-- If this is SQL 2005 or later then...
if (server.Information.Version.Major > 8) {
    -- Do something here...
}

You can reuse SQL Connnection Properties interface:

 

You will need reference to two DLLs:

 Two controls on your form:

 

and some code:

#region Database Connection Strings

private string filterConnectionString(string text)
{
	System.Text.RegularExpressions.Regex regex;
	regex = new System.Text.RegularExpressions.Regex("(Password|PWD)\\s*=([^;]*)([;]*)",
	    System.Text.RegularExpressions.RegexOptions.IgnoreCase);
	return regex.Replace(text, "$1=<hidden>$3");
}

/// <summary>
/// Database Administrative Connection String.
/// </summary>
private string dbaConnectionString
{
	get { return m_dbaConnectionString; }
	set
	{
		this.m_dbaConnectionString = value;
		DbaConnectionStringTextBox.Text = filterConnectionString(value);
	}
}
private string m_dbaConnectionString;

private void DbaConnectionButton_Click(object sender, EventArgs e)
{
	Microsoft.Data.ConnectionUI.DataConnectionDialog dcd;
	dcd = new Microsoft.Data.ConnectionUI.DataConnectionDialog();
	//Adds all the standard supported databases
	//DataSource.AddStandardDataSources(dcd);
	//allows you to add datasources, if you want to specify which will be supported 
	dcd.DataSources.Add(Microsoft.Data.ConnectionUI.DataSource.SqlDataSource);
	dcd.SetSelectedDataProvider(Microsoft.Data.ConnectionUI.DataSource.SqlDataSource,
	    Microsoft.Data.ConnectionUI.DataProvider.SqlDataProvider);
	dcd.ConnectionString = this.dbaConnectionString;
	Microsoft.Data.ConnectionUI.DataConnectionDialog.Show(dcd);
	if (dcd.DialogResult == DialogResult.OK)
	{
		this.dbaConnectionString = dcd.ConnectionString;
	}
}

#endregion

SQL XML Comments

Use XML Comments for SQL server similar to object oriented languages. In this case comments can be parsed into CHM or XHTML help files easier. In SQL 2005 parameter descriptions can be pulled out from extended properties of cells. 

--- <summary>
--- Insert new record into table.
--- </summary>
--- <param name="RecordId">Unique record Id. Use '-1' for auto value.</param>
--- <param name="RecordGuid">Set Global Unique Identifier.</param>
--- <param name="SomeValue">Set record value.</param>
--- <param name="RecordEnable">Enable or disable record.</param>
--- <remarks>
--- History:
---     2007-11-02 - Created by John Smith
--- </remarks>
CREATE PROCEDURE [dbo].[solution_Category_InsertRecord] (
     @RecordId Int,
     @RecordGuid UniqueIdentifier,
     @SomeValue NVarChar(200),
     @RecordEnabled Bit )
AS
-- Stored procedure starts here...

Installation and Update Tips

Uninstallation  / Update / Installation must be done in this order to avoid relation conflicts

  1. Drop Foreign Key Constraints.
  2. Drop Default Value Constraints.
  3. Drop Check Constraints.
  4. Drop Primary Key Constraints.
  5. Drop Indexes.
  6. Alter Tables.
  7. Create Indexes.
  8. Create Primary Key Constraints.
  9. Create Check Constraints.
  10. Create Default Value Constraints.
  11. Create Foreign Key Constraints.

Fix link between SQL login and database user after attaching database.

USE [DataBaseName]
-- Create SQL Server Login
CREATE LOGIN [DatabaseAdminUserName] WITH
PASSWORD=N'password',
DEFAULT_DATABASE=[DataBaseName],
DEFAULT_LANGUAGE=[us_english],
CHECK_EXPIRATION=OFF,
CHECK_POLICY=OFF
GO
ALTER LOGIN [DatabaseAdminUserName] DISABLE
-- Map SQL Server Login to Database User.
exec sp_change_users_login 'AUTO_FIX', 'DatabaseAdminUserName'
ALTER LOGIN [DatabaseAdminUserName] ENABLE

Execute all .sql files in a folder without executing them individually from command line

for %z in (*.sql) do osql -S <server> -U <username> -P <password> -d <datbasename> -i %z

READUNCOMMITTED or NOLOCK

  With SELECT we can use Lock Hint WITH (READUNCOMMITTED) or WITH (NOLOCK). NOLOCK with SELECT is better because word READUNCOMMITTED is reserved for other purpose. So:

    a) Use READ [UN]COMMITTED to SET Transaction Isolation Level. 
    b) Use NOLOCK with SELECT for single "auto commit" transaction.

In this way you are logically separating how lock is used (on single query or on Transaction/Procedure). This helps when you will try to find stored procedures by different lock area or use these words in conversation with other developers.

File Names

Object Oriented Style

Stored Procedures:
Naming: <SolutionName>_<CategoryName>_<ActionName><ObjectName>[By<Condition>]
Example: crm_Company_GetEmployeesByDepartment

Type prefix were replaced by solution name because prefix lost is usefulness with new version of SQL server and object oriented languages which separates objects by type perfectly. Also solution name makes much more sense when couple of solutions are integrated into one database.

There are two naming styles for database objects:

Hungarian Style (Outdated):

Stored Procedures:
Naming: <TypePrefix>_<CategoryName>_<ActionName><ObjectName>[By<Condition>]
Example: up_Company_GetEmployeesByDepartment

Ext Pfx Name Parent Naming
TAB TB Table Database TB_ + Table Name
PRC UP User Procedure Database UP_ + Action + Object [+ Condition]
PRC SP Stored Procedure Database SP_ + Action + Object [+ Condition]
XP eXtended Procedure Database XP_ + Action + Object [+ Condition]
VIW VI View Database VI_ + Table Name
FK Foreign Key Constraint Table FK_ + Foreign Table Name + _ + Primary Key Table Name
DF Default Constraint Column DF_+ Table Name + _ + Column Name
PK Primary Key Constraint Index PK_ + Table Name [+ _ + Column Name]
CK Check Constraint Index CK_ + Table Name [+ _ + Column Name]
UQ Unique Constraint Index UQ_ + Table Name [+ _ + Column Name]
IDX IX Index Table IX_ + Table Name [+ _ + Column Name]
TRG TR Trigger Database, Table
LGN Login  
USR User  
DBS Database  

Use 'UP_' as prefix for user stored procedures because Microsoft use 'sp_' prefix for shared stored procedures.
Use '#' prefix for temporary table names inside scripts.

File Names

Type Extension
Function ObjectName.function.sql
Index ObjectName.index.sql
Stored Procedure ObjectName.proc.sql
Table ObjectName.table.sql
Trigger ObjectName.trigger.sql
View ObjectName.view.sql
Primary Key Constraint ObjectName.pkey.sql
Foreign Key Constraint ObjectName.fkey.sql
Unique Key Constraint ObjectName.ukey.sql
Check Constraint ObjectName.chkconst.sql
Default Constraint ObjectName.defconst.sql
Statistic ObjectName.statistic.sql

FileLocation
Main Database File D:\SQLDATA\DatabaseName.MDF
Non-Primary Database File D:\SQLDATA\DatabaseName.NDF
Log Data File D:\SQLLOGS\DatabaseName.LDF
Virtual Log File VLF files exists inside *.LDF file
Full Database Backup File D:\SQLBACK\DatabaseName_yyyMMdd_HHmmss_full.BAK
Differential Database Backup File D:\SQLBACK\DatabaseName_yyyMMdd_HHmmss_diff.DIF
Transaction Log Backup File D:\SQLBACK\DatabaseName_yyyMMdd_HHmmss_logs.TRN

Other Rules

Prefix Name Examples
Insert INSERT one record into table globalization_Languages_InsertRecord *
Select SELECT one record from table globalization_Languages_SelectRecord *
Select SELECT all records from table globalization_Languages_SelectRecords
Update UPDATE one record inside table globalization_Languages_UpdateRecord *
Delete DELETE one record from table globalization_Languages_DeleteRecord *

* - Procedures used to create .NET DataSet TableAdapter object.

Otherwise use these prefixes:

Prefix Name Examples
Add Add record(s) to one or multiple tables. aspnet_UsersInRoles_AddUsersToRoles
Get Get record(s) from one or multiple tables. aspnet_Membership_GetUserByName
Set Set record(s) of one or multiple tables. aspnet_Membership_SetPassword
Upsert Insert or update record by unique condition/index. crm_UserTicks_UpsertUserTick
Create Insert record and adjust records in other tables. aspnet_Membership_CreateUser
Modify Update record and adjust records in other tables. crm_Company_ModifyUserInfo
Remove Delete record and adjust records in other tables. aspnet_Setup_RemoveAllRoleMembers
Change Replace cell value in table. crm_Tickets_ChangeTicketStatus

CREATE PROCEDURE [dbo].[solution_Category_InsertRecord] (
    @RecordId int,
    @RecordGuid uniqueIdentifier,
    @SomeValue int,
    @RecordEnabled bit
) AS

-- You can call this procedure with this command.
-- EXEC solution_Category_InsertRecord null, null, 135, 1

-- If @RecordGuid was not specified then generate new one.
IF @RecordGuid = '00000000-0000-0000-0000-000000000000' 
SET @RecordGuid = newId()

INSERT INTO [dbo].[SomeTable] (
    [RecordId],
    [RecordGuid],
    [SomeValue],
    [RecordEnabled]
) VALUES (
    @RecordId,
    @RecordGuid,
    @SomeValue,
    @RecordEnabled
)

-- Use SCOPE_IDENTITY() to return [RecordId] value if [RecorId] if auto-seeded.

-- Return new values so user don't need to query database again to get them.
SELECT @RecordId as 'RecordId'
-- Or you can select all cells
SELECT * FROM dbo.SomeTable WHERE [RecordId] = @RecordId